`include "acsdefs.v"

module user (
             // *** Globale Signale
             CLK,                 // Takt 
             RESET,               // Chip Reset

             // *** Slave-Interface
             ADDRESSED,           // RC angesprochen im Slave-Mode
             WRITE,               // Schreibzugriff?
             DATAIN,              // Dateneingang
             DATAOUT,             // Datenausgang
             ADDRESS,             // Adresseingang
             IRQ,                 // Loest Interrupt (IRQ) an CPU aus

             // *** Stream-Interface
             STREAM_READ,        // Read-Datenbus
             STREAM_WRITE_PROG,  // Write-program-Datenbus
             STREAM_STALL,       // Stallsignale
             STREAM_ENABLE,      // Enables
             STREAM_FLUSH,       // Flushsignale
             STREAM_PROG         // Programsignale
             );

   // Schnittstelle  *****************************************************

   // Eingaenge
   input                     CLK;
   input                     RESET;
   input                     ADDRESSED;
   input                     WRITE;
   input [31:0] 	     DATAIN;
   input [23:2] 	     ADDRESS;
   input [`STREAM_DATA_BUS]  STREAM_READ;
   input [`STREAM_CNTL_BUS]  STREAM_STALL;
   
   // Ausgaenge
   output [31:0] 	     DATAOUT;
   output [`STREAM_DATA_BUS] STREAM_WRITE_PROG;
   output [`STREAM_CNTL_BUS] STREAM_ENABLE;
   output [`STREAM_CNTL_BUS] STREAM_FLUSH;
   output [`STREAM_CNTL_BUS] STREAM_PROG;
   output                    IRQ;

   // Deklaration fuer Stream-Interface
   wire [`STREAM_DATA_BUS]   STREAM_READ;
   wire [`STREAM_DATA_BUS]   STREAM_WRITE_PROG;
   wire [`STREAM_CNTL_BUS]   STREAM_STALL;
   wire [`STREAM_CNTL_BUS]   STREAM_ENABLE;
   reg [`STREAM_CNTL_BUS]    STREAM_FLUSH;
   reg [`STREAM_CNTL_BUS]    STREAM_PROG;

   // Konstantendefinitionen ***************************************************

   // FSM Zustaende
`define STATE_PROG_START_A  0   // programmiere Startadressen in Streams
`define STATE_PROG_COUNT_A  1   // programmiere Datensatzzahl in Streams
`define STATE_PROG_STEP_A   2   // programmiere Schrittweite in Streams
`define STATE_PROG_WIDTH_A  3   // programmiere Zugriffsbreite in Streams
`define STATE_PROG_MODE_A   4   // programmiere Betriebsart in Streams
`define STATE_COMPUTE_A     5   // fuehre Berechnung auf Streamdaten aus
`define STATE_WAIT_A        6   // Warte einen Takt zum Entleeren des Streams
`define STATE_SHUTDOWN_A    7   // Beende Berechnung
`define STATE_PROG_START_B  8   // programmiere Startadressen in Streams
`define STATE_PROG_COUNT_B  9   // programmiere Datensatzzahl in Streams
`define STATE_PROG_STEP_B   10  // programmiere Schrittweite in Streams
`define STATE_PROG_WIDTH_B  11  // programmiere Zugriffsbreite in Streams
`define STATE_PROG_MODE_B   12  // programmiere Betriebsart in Streams
`define STATE_COMPUTE_B     13  // fuehre Berechnung auf Streamdaten aus
`define STATE_WAIT_B        14  // Warte einen Takt zum Entleeren des Streams
`define STATE_SHUTDOWN_B    15  // Beende Berechnung

   // Beginn der Anwendung *****************************************************

   // Wurde Anwendung gestartet?
   reg 			     START;
   // Anfangsadresse der Eingabedaten
   reg [31:0] 		     SOURCEADDR;
   // Anfangsadresse der Ausgabedaten
   reg [31:0] 		     DESTADDR;
   // Laenge der Daten (als 32b Worte)
   reg [31:0] 		     COUNT_A, COUNT_B;
   // Loest Interrupt aus
   reg 			     IRQSTATE;
   // Aktueller Zustand der Anwendung
   reg [4:0] 		     STATE;
   // Sind Streams gestartet?
   reg 			     STREAMSTART;
   // Programmierdaten-Register fuer Streams
   reg [31:0] 		     STREAM_PROGDATA_0;
   reg [31:0] 		     STREAM_PROGDATA_1;

   // Daten
   wire [31:0] 		     WRITE_DATA;

   // Abkuerzung fuer Registernummer 0 ... 15
   wire [3:0] 		     REGNUM = ADDRESS[5:2];

   // Streams laufen, nachdem sie gestartet worden sind und solange
   // noch Daten zu bearbeiten sind.
   wire 		     RUNNING = (STREAMSTART && (COUNT_A != 0) && (COUNT_B != 0)) ? 1 : 0;
   reg [15:0] 	   dividend;
   reg [7:0] 	   divisor;
   wire [15:0] 	   quot;
   wire [7:0] 	   remd;

   reg [7:0] 		     minimum, maximum;
   reg [15:0] 	   stepSize;
   wire [31:0] 		     RESULT = ((((STREAM_READ[`STREAM_0] & 'hff)-minimum) << 4)*stepSize)>>8;

reg 	   overtakeQuot [0:17];

generate
      genvar 	   i;
      for (i = 1; i < 18; i = i + 1) begin
	 always @(posedge CLK or posedge RESET) begin
	    if ((RESET) || (WRITE == 0)) begin
	       overtakeQuot[i] <= 0;
	    end else begin
	       overtakeQuot[i] <= overtakeQuot[i - 1];
	    end
	 end
      end
   endgenerate

   PipelinedDivider pd(.dividend(dividend),
		       .divisor(divisor),
		       .quot(quot),
		       .remd(remd),
		       .clk(CLK),
		       .rfd());
   
   // Flusskontrolle zwischen Ein- und Ausgabedatenstrom
   flowcontrol FC (
                   CLK,                   // Takt
                   RUNNING,               // Streams laufen lassen?
                   STREAM_STALL[0],       // Haengt Stream 0 (Eingabe-Strom)?
                   STREAM_STALL[1],       // Haengt Stream 1 (Ausgabe-Strom)?
		   /** Aendern **/ RESULT, // STREAM_READ[`STREAM_0],// Von Anwendung zu schreibende Daten
                   STREAM_ENABLE[0],      // Stream 0 starten oder anhalten
                   STREAM_ENABLE[1],      // Stream 1 starten oder anhalten
                   WRITE_DATA             // Eingangsdaten fuer Ausgabe-Strom
                   );

   // Gebe IRQSTATE Register an CPU IRQ-Leitung aus
   assign IRQ = IRQSTATE;
   
   // Gebe immer das gerade adressierte Register aus.
   // Nicht benoetigte Register geben eine Magic-Number
   // und den aktuellen IRQ-Status im MSB zurueck
   wire [31:0] 		     DATAOUT =   (REGNUM === 4'h0) ? SOURCEADDR
                             : (REGNUM === 4'h1) ? DESTADDR
                             : (REGNUM === 4'h2) ? COUNT_B
			     : { maximum, minimum, stepSize}; 
   // : (32'h00C0FFEE | (IRQSTATE << 31)); 

   // Schalte Streams zwischen Programmier- und Datenbetrieb um
   // Stream0 ist Lese-Stream, sein Eingang kann immer im Programmierbetrieb sein
   assign STREAM_WRITE_PROG[`STREAM_0] = STREAM_PROGDATA_0;
   // Stream1 ist Schreib-Stream, hier muss der Eingang umgeschaltet werden
   assign STREAM_WRITE_PROG[`STREAM_1] = (STREAM_PROG[1]) ?
                                         STREAM_PROGDATA_1 : WRITE_DATA;
   
   // Controller FSM ueberwacht gesamte Anwendung
   always @(posedge CLK or posedge RESET) begin
      // Initialisiere Register bei chip-weitem Reset
      if (RESET) begin
	 STATE             <= `STATE_PROG_START_A; 
	 IRQSTATE          <= 0;
	 STREAMSTART       <= 0; STREAM_PROG       <= 0;
	 STREAM_FLUSH      <= 0; STREAM_PROGDATA_0 <= 0;
	 STREAM_PROGDATA_1 <= 0; SOURCEADDR        <= 0;
	 DESTADDR          <= 0; COUNT_A           <= 0;
	 COUNT_B           <= 0; START             <= 0;
	 minimum            = 255; maximum          = 0;
	 overtakeQuot[0]   <= 0; stepSize          <=0;
	 dividend <= 16'hffff;
	 divisor <= 8'h01;
	 
	 
	 // Schreibzugriff auf RC, schreibe in entsprechendes Register
      end else if (ADDRESSED & WRITE) begin
	 case (REGNUM)
           0: SOURCEADDR   <= DATAIN;
           1: DESTADDR     <= DATAIN;
           2: begin
	      COUNT_A      <= DATAIN << 2;
	      COUNT_B      <= DATAIN << 2;
	   end
           3: begin 
              START        <= 1; // Startkommando, beginne Ausfuehrung
           end
           default: ;
	 endcase
      end else begin

	 if (overtakeQuot[17] == 1) begin
	    stepSize <= quot;
	 end
	 
	 // CPU hat Berechnung gestartet, keine Slave-Mode Zugriffe mehr moeglich
	 if (START) begin
            case (STATE)
	      `STATE_PROG_START_A:
		begin
		   // Beide Streams in Programmierungsmodus schalten
		   STREAM_PROG[1:0]  <= 2'b11;
		   // Anfangsadresse fuer Stream 0 schreiben
		   STREAM_PROGDATA_0 <= SOURCEADDR;
		   // Anfangsadresse fuer Stream 1 schreiben
		   STREAM_PROGDATA_1 <= DESTADDR;
		   // FSM weitersetzen
		   STATE <=  `STATE_PROG_COUNT_A;
		end
	      `STATE_PROG_COUNT_A:
		begin
		   // Anzahl Datensaetze - 1 (bei beiden Streams gleich)
		   STREAM_PROGDATA_0 <= COUNT_A - 1;
		   STREAM_PROGDATA_1 <= COUNT_A - 1;
		   // FSM weitersetzen
		   STATE <=  `STATE_PROG_STEP_A;
		end
	      `STATE_PROG_STEP_A:
		begin
		   // Schrittweite: 1 Datensatz (bei beiden Streams gleich)
		   STREAM_PROGDATA_0 <= 1;
		   STREAM_PROGDATA_1 <= 1;
		   // FSM weitersetzen
		   STATE <=  `STATE_PROG_WIDTH_A;
		end 
	      `STATE_PROG_WIDTH_A:
		begin
		   // Wordbreite der Zugriffe: 32b (bei beiden Streams gleich)
		   STREAM_PROGDATA_0 <= 8;//`STREAM_32B;
		   STREAM_PROGDATA_1 <= 8;//`STREAM_32B;
		   // FSM weitersetzen
		   STATE <=  `STATE_PROG_MODE_A;		 
		end
	      `STATE_PROG_MODE_A: 
		begin
		   // Zugriffsart fuer Stream 0: Lesen
		   STREAM_PROGDATA_0 <= `STREAM_READ;
		   // Zugriffsart fuer Stream 1: Schreiben
		   STREAM_PROGDATA_1 <= `STREAM_WRITE;
		   // FSM weitersetzen
		   STATE <=  `STATE_COMPUTE_A;		 
		end
	      `STATE_COMPUTE_A: begin
		 // Programmiermodus fuer beide Streams abschalten
		 STREAM_PROG[1:0]   <= 0;
		 // Beide Streams starten (via flowcontrol-Modul)
		 STREAMSTART <= 1;

		 if (COUNT_A != COUNT_B) begin // erst ab der zweiten Iteration
		    
		    if ((STREAM_READ[`STREAM_0] & 'hff) > maximum) begin
		       maximum = STREAM_READ[`STREAM_0];
		    end
		    if ((STREAM_READ[`STREAM_0] & 'hff) < minimum) begin
		       minimum = STREAM_READ[`STREAM_0];
		    end

		    dividend <= 255 << 4;
		    divisor <= (maximum - minimum);
		    overtakeQuot[0] <= 1;
		 end
		 
		 // Alle Datensaetze bearbeitet?
		 if (COUNT_A == 0) begin
                    // Dann beide Streams stoppen
                    STREAMSTART <= 0;
                    // Falls Write-Stream fertig
                    if (!STREAM_STALL[1]) begin
		       // alle noch gepufferten Daten wirklich schreiben
		       STREAM_FLUSH[1] <= 1;
		       // FSM weitersetzen
		       STATE <= `STATE_WAIT_A;
		       COUNT_A <= 1;
                    end
		 end else if (STREAM_ENABLE[0] & ~STREAM_STALL[0]) begin
                    // Nur dann einen Datensatz als bearbeitet zaehlen,
                    // wenn Stream 0 aktiv liest (ENABLE) und nicht haengt (!STALL)
                    COUNT_A <= COUNT_A - 1;
		 end
	      end  
	      `STATE_WAIT_A: begin
		 STATE <=  `STATE_SHUTDOWN_A;
	      end
	      `STATE_SHUTDOWN_A: begin
		 if (!STREAM_STALL[1]) begin
                    // ja, Leerung beenden
                    STREAM_FLUSH[1] <= 0;
		    STATE <=  `STATE_PROG_START_B;
		 end
	      end
              `STATE_PROG_START_B: 
		begin
		   // Beide Streams in Programmierungsmodus schalten
		   STREAM_PROG[1:0]  <= 2'b11;
		   // Anfangsadresse fuer Stream 0 schreiben
		   STREAM_PROGDATA_0 <= SOURCEADDR;
		   // Anfangsadresse fuer Stream 1 schreiben
		   STREAM_PROGDATA_1 <= DESTADDR;
		   // FSM weitersetzen
		   STATE <= `STATE_PROG_COUNT_B;
		end
              `STATE_PROG_COUNT_B: 
		begin
		   // Anzahl Datensaetze - 1 (bei beiden Streams gleich)
		   STREAM_PROGDATA_0 <= COUNT_B - 1;
		   STREAM_PROGDATA_1 <= COUNT_B - 1;
		   // FSM weitersetzen
		   STATE <= `STATE_PROG_STEP_B;
		end
              `STATE_PROG_STEP_B: 
		begin
		   // Schrittweite: 1 Datensatz (bei beiden Streams gleich)
		   STREAM_PROGDATA_0 <= 1;
		   STREAM_PROGDATA_1 <= 1;
		   // FSM weitersetzen
		   STATE <= `STATE_PROG_WIDTH_B;
		end
              `STATE_PROG_WIDTH_B: 
		begin
		   // Wordbreite der Zugriffe: 32b (bei beiden Streams gleich)
		   STREAM_PROGDATA_0 <= 8;//`STREAM_32B;
		   STREAM_PROGDATA_1 <= 8;//`STREAM_32B;
		   // FSM weitersetzen
		   STATE <= `STATE_PROG_MODE_B;
		end
              `STATE_PROG_MODE_B: 
		begin
		   // Zugriffsart fuer Stream 0: Lesen
		   STREAM_PROGDATA_0 <= `STREAM_READ;
		   // Zugriffsart fuer Stream 1: Schreiben
		   STREAM_PROGDATA_1 <= `STREAM_WRITE;
		   // FSM weitersetzen
		   STATE <= `STATE_COMPUTE_B;
		end
              `STATE_COMPUTE_B: 
		begin
		   // Programmiermodus fuer beide Streams abschalten
		   STREAM_PROG[1:0]   <= 0;
		   // Beide Streams starten (via flowcontrol-Modul)
		   STREAMSTART <= 1;

		   // Alle Datensaetze bearbeitet?
		   if (COUNT_B == 0) begin
                      // Dann beide Streams stoppen
                      STREAMSTART <= 0;
                      // Falls Write-Stream fertig
                      if (!STREAM_STALL[1]) begin
			 // alle noch gepufferten Daten wirklich schreiben
			 STREAM_FLUSH[1] <= 1;
			 // FSM weitersetzen
			 STATE <= `STATE_WAIT_B;
			 COUNT_A <= 0;
                      end
		   end else if (STREAM_ENABLE[0] & ~STREAM_STALL[0])
                     // Nur dann einen Datensatz als bearbeitet zaehlen,
                     // wenn Stream 0 aktiv liest (ENABLE) und nicht haengt (!STALL)
                     COUNT_B <= COUNT_B - 1;
		end
              `STATE_WAIT_B:
		begin
                   STATE <= `STATE_SHUTDOWN_B;
		end
              `STATE_SHUTDOWN_B: 
		begin
                   // Ist Schreibpuffer schon komplett geleert?
                   if (!STREAM_STALL[1]) begin
                      // ja, Leerung beenden
                      STREAM_FLUSH[1] <= 0;
                      // CPU durch IRQ Fertigwerden der RC anzeigen
                      IRQSTATE <= 1;
                      // FSM stoppen (RC jetzt wieder im Slave-Mode)
                      START <= 0;
                      // FSM auf Startzustand zuruecksetzen
                      STATE <= `STATE_PROG_START_B;
                   end
		end
              // sollte nicht auftreten, nur fuer Logikoptimierung
              default: STATE <='bx;
            endcase
	 end
	 // Bei jedem Zugriff auf RC im Slave-Mode, IRQ ausschalten
	 else if (ADDRESSED)
	   IRQSTATE <= 0;
      end
   end

endmodule
